const sha256 = require('sha256');
const nodeUrl = process.argv[3];
const { v4: uuid } = require('uuid');

const HASH_HEAD_TAG = '0000'

function Blockchain() {
    this.chain = [];
    this.pending_transactions = [];

    this.node_url = nodeUrl;
    this.network_nodes = []

    this.createNewBlock(0, '0', '0');
}

Blockchain.prototype.createNewBlock = function(nonce, previous_block_hash, hash) {
    const newBlock = {
        index: this.chain.length + 1,
        timestamp: Date.now(),
        transactions: this.pending_transactions,
        nonce: nonce,
        previous_block_hash: previous_block_hash,
        hash: hash
    };

    this.pending_transactions = [];
    this.chain.push(newBlock);

    return newBlock;
}

Blockchain.prototype.getLastBlock = function() {
    return this.chain[this.chain.length - 1];
}

Blockchain.prototype.createNewTransaction = function(amount, sender, recipient) {
    const newTransaction = {
        amount: amount,
        sender: sender,
        recipient: recipient,
        transaction_id: uuid().split('-').join('')
    }
    return newTransaction;
}

Blockchain.prototype.addPendingTransaction = function(transaction) {
    this.pending_transactions.push(transaction);
    return this.getLastBlock()['index'] + 1;
}

Blockchain.prototype.hash = function(previous_block_hash, block_data, nonce) {
    const data_str = previous_block_hash + JSON.stringify(block_data) + nonce.toString();
    const hash = sha256(data_str);

    return hash;
}

Blockchain.prototype.proofOfWork = function(previous_block_hash, current_block_data) {
    let nonce = 0;
    let hash = '';

    do {
        nonce++;
        hash = this.hash(previous_block_hash, current_block_data, nonce);
        // console.log(hash);
    } while (hash.substring(0, HASH_HEAD_TAG.length) != HASH_HEAD_TAG);

    return nonce;
}

Blockchain.prototype.isChainValid = function(blockchain) {
    for (var i = 1; i < blockchain.length; ++i) {
        const currentBlock = blockchain[i];
        const prevBlock = blockchain[i - 1];
        const currentBlockData = {
            transactions: currentBlock['transactions'],
            index: currentBlock['index']
        }
        const currentBlockHash = this.hash(prevBlock['hash'], currentBlockData, currentBlock['nonce']);

        if (currentBlockHash !== currentBlock['hash']) {
            return false; // invalid
        }
        if (currentBlockHash.substring(0, HASH_HEAD_TAG.length) != HASH_HEAD_TAG) {
            return false; // invalid
        }
        if (currentBlock['previous_block_hash'] !== prevBlock['hash']) {
            return false; // invalid
        }
    }

    if (blockchain.length < 1) {
        return false; // invalid
    }

    const genesisBlock = blockchain[0];
    const correctNonce = genesisBlock['nonce'] === 0;
    const correctPreviousBlockHash = genesisBlock['previous_block_hash'] === '0';
    const correctHash = genesisBlock['hash'] === '0';
    const correctTransactions = genesisBlock['transactions'].length === 0;
    if (!correctNonce || !correctPreviousBlockHash || !correctHash || !correctTransactions) {
        return false; // invalild
    }

    return true; // valid
}

Blockchain.prototype.getBlock = function(blockHash) {
    for (const block of this.chain) {
        if (block['hash'] === blockHash) {
            console.log(`find block hash = ${blockHash} at ${block['index']}`);
            return block;
        }
    }
    console.log(`can not find block with hash = ${blockHash}`)
    return null;
}

Blockchain.prototype.getTransaction = function(transactionId) {
    for (const block of this.chain) {
        for (const transaction of block.transactions) {
            if (transaction.transaction_id === transactionId) {
                console.log(`find transaction id = ${transactionId} at ${block['index']}`);
                return {
                    transaction: transaction,
                    block: block
                };
            }
        }
    }
    console.log(`can not find transaction with id = ${transactionId}`)
    return {
        transaction: null,
        block: null
    };
}

Blockchain.prototype.getAddressData = function(address) {
    const addressTransactions = [];
    this.chain.forEach(block => {
        block.transactions.forEach(transaction => {
            if (transaction.sender === address || transaction.recipient === address) {
                addressTransactions.push(transaction);
            }
        })
    });

    let balance = 0;
    addressTransactions.forEach(transaction => {
        if (transaction.recipient == address) {
            balance += transaction.amount;
        } else if (transaction.sender == address) {
            balance -= transaction.amount;
        }
    });

    return {
        transactions: addressTransactions,
        balance: balance
    };
};

module.exports = Blockchain;